Example PRO

import { Button, Navigation, NavigationStack, Script, useEffect, Path, MagnifyGesture, useObservable, VideoRecorderPreviewView, VStack, Toolbar, ToolbarItem, ToolbarItemGroup } from "scripting"

const recorder = VideoRecorder

function View() {
  // Access dismiss function.
  const dismiss = Navigation.useDismiss()
  const state = useObservable<VideoRecorder.State>("idle")
  const displayZoom = useObservable(1)
  const startZoom = useObservable(1)
  const volume = useObservable(0)
  const toastVisible = useObservable(false)
  const toastMessage = useObservable("")
  const position = useObservable<VideoRecorder.CameraPosition>("back")

  function showToast(message: string) {
    toastVisible.setValue(true)
    toastMessage.setValue(message)
  }

  useEffect(() => {

    const listener = (value: number, old: number) => {
      console.log("old:", old, "new:", value)
      volume.setValue(value)
    }
    SharedAudioSession.addOutputVolumeListener(listener)

    return () => {
      SharedAudioSession.removeOutputVolumeListener(listener)
    }
  }, [])

  async function prepare() {
    await recorder.prepare({
      camera: {
        position: position.value,
        // preferredTypes: ["triple"]
      },
      frameRate: 30,
      audioEnabled: true,
      orientation: "portrait",
      sessionPreset: "high",
      videoCodec: "appleProRes4444XQ",
      // autoConfigAppAudioSession: false
    })
  }

  useEffect(() => {
    prepare().then(() => {
      recorder.start(
        Path.join(
          FileManager.documentsDirectory,
          "test.mov"
        )
      )
    }).catch(e => {
      showToast("Failed to prepare:" + String(e))
    })
    recorder.addStateListener((
      newState, details
    ) => {
      state.setValue(newState)

      if (newState === "ready") {
        // recorder.rampZoomFactor(0.5, 4
      }

      if (newState === "failed") {
        Dialog.alert(details!)
      }

    })

    return () => {
      recorder.reset()
    }
  }, [])

  return <NavigationStack>
    <VStack
      toolbar={
        <Toolbar>
          <ToolbarItem
            placement="topBarLeading"
          >
            <Button
              title="Done"
              systemImage="xmark"
              action={dismiss}
            />
          </ToolbarItem>
          <ToolbarItem
            placement="topBarTrailing"
          >
            <Button
              title="Toggle Torch"
              action={() => {
                if (recorder.torchMode === "on") {
                  recorder.setTorchMode("off")
                } else {
                  recorder.setTorchMode("on")
                }
              }}
            />
          </ToolbarItem>
          <ToolbarItemGroup
            placement="bottomBar"
          >
            {state.value == "idle" && <Button
              title="Prepare"
              action={async () => {
                try {
                  await recorder.prepare()
                } catch (e) {
                  showToast("Failed to prepare:" + e)
                }
              }}
            />}
            {(state.value == "idle" || state.value == "ready") && <Button
              title="Toggle Camera"
              action={async () => {
                try {
                  position.setValue(
                    position.value == "back"
                      ? "front"
                      : "back"
                  )
                  await recorder.reset()
                  await prepare()
                } catch (e) {
                  showToast("Failed to toggle camera:" + e)
                }
              }}
            />}
            {state.value == "ready" && <Button
              title="Start"
              action={async () => {
                try {
                  await recorder.start(
                    Path.join(
                      FileManager.documentsDirectory,
                      "test.mov"
                    )
                  )
                } catch (e) {
                  showToast("Failed to start:" + e)
                }
              }}
            />}
            {state.value == "recording" && <Button
              title="Pause"
              action={async () => {
                try {
                  await recorder.pause()
                } catch (e) {
                  console.error("Failed to call pause", e)
                }
              }}
            />}
            {state.value == "paused" && <Button
              title="Resume"
              action={async () => {
                try {
                  await recorder.resume()
                } catch (e) {
                  showToast("Failed to resume:" + e)
                }
              }}
            />}
            {state.value == "recording" && <Button
              title="Cancel"
              action={async () => {
                try {
                  await recorder.cancel()
                } catch (e) {
                  showToast("Failed to cancel:" + e)
                }
              }}
            />}
            {state.value == "recording" && <Button
              title="Stop"
              action={async () => {
                try {
                  await recorder.stop()
                  await prepare()
                } catch (e) {
                  showToast("Failed to stop: " + e)
                }
              }}
            />}
          </ToolbarItemGroup>
        </Toolbar>
      }
      toast={{
        isPresented: toastVisible,
        message: toastMessage.value,
        position: "top"
      }}
    >

      {/*  <Text>State: {state.value}</Text>
      <Text>OutputVolume: {volume.value}</Text> */}
      <VideoRecorderPreviewView
        key="videoRecorder"
        ignoresSafeArea
        frame={{
          width: Device.screen.width
        }}
        aspectRatio={{
          value: 3 / 4,
          contentMode: "fill"
        }}
        gesture={
          MagnifyGesture()
            .onChanged(details => {
              recorder.setZoomFactor(
                details.magnification * startZoom.value
              )
              displayZoom.setValue(
                recorder.displayZoomFactor
              )
            })
            .onEnded(details => {
              recorder.setZoomFactor(
                details.magnification * startZoom.value
              )
              displayZoom.setValue(
                recorder.displayZoomFactor
              )
              startZoom.setValue(
                recorder.currentZoomFactor
              )
            })
        }
      />
    </VStack >
  </NavigationStack >
}

async function run() {
  // Present view.
  await Navigation.present({
    element: <View />
  })

  // Avoiding memory leaks.
  Script.exit()
}

run()